
package com.limoilux.cobolux.core.builder;

import java.io.IOException;
import java.io.InputStream;
import java.util.StringTokenizer;

import com.limoilux.cobolux.core.CoboluxCoreUtils;
import com.limoilux.cobolux.core.ProblemMarker;

public class OpenCobolCompiler extends CobolCompiler
{
	private static final int SEGMENTATION_FAULT = 139;
	private final Runtime runtime;
	private final String compilerPath;

	public OpenCobolCompiler(String compilerPath)
	{
		super();

		this.runtime = Runtime.getRuntime();
		this.compilerPath = compilerPath;
	}

	@Override
	public boolean compile(String sourceFile)
	{
		Process process = null;
		InputStream in = null;
		ProblemMarker marker = null;
		String shortName = null;
		String binFile = null;
		String errorLine = null;
		String errorOutput = null;
		String standardOutput = null;
		String cmd = null;
		StringTokenizer tokenizer = null;

		boolean success = true;
		long time = 0;
		int exitValue = 0;

		try
		{
			this.reset();

			time = System.currentTimeMillis();

			shortName = CoboluxCoreUtils.removeExtension(sourceFile);
			binFile = shortName + "." + this.getBinaryExtension();
			this.setExecutable(binFile);

			cmd = this.buildCompilationCommand(sourceFile, binFile);

			this.deleteOldBin(binFile);

			process = this.runtime.exec(cmd);

			in = process.getErrorStream();
			errorOutput = this.readInput(in);
			tokenizer = new StringTokenizer(errorOutput, "\n");

			while (tokenizer.hasMoreTokens())
			{
				errorLine = tokenizer.nextToken();
				marker = this.processErrorLine(errorLine);
				this.addMarker(marker);
			}

			// Jeter la sortie standard
			in = process.getInputStream();
			standardOutput = this.readInput(in);

			exitValue = process.waitFor();

			marker = this.processExitValue(exitValue);
			if (marker != null)
			{
				this.addMarker(marker);
				success = false;
			}

			time = System.currentTimeMillis() - time;

			this.setExecutionTime(time);
			this.setExitValue(exitValue);

			this.setStandardOutput(standardOutput);
			this.setErrorOutput(errorOutput);
		}
		catch (IOException e)
		{
			success = false;
			e.printStackTrace();
		}
		catch (InterruptedException e)
		{
			success = false;
			e.printStackTrace();
		}
		finally
		{
			if (process != null)
			{
				process.destroy();
				process = null;
			}
		}

		return success;
	}

	@Override
	public String buildCompilationCommand(String source, String binary)
	{
		String cmd = "";

		cmd += this.compilerPath;
		cmd += " -std=mf";
		cmd += " -x";
		cmd += " -g";
		cmd += " -Wall";
		cmd += " -Wtruncate";
		cmd += " -Wcall-params";
		cmd += " -o " + binary;
		cmd += " " + source;

		System.out.println(cmd);

		return cmd;
	}

	private ProblemMarker processExitValue(int exitValue)
	{
		ProblemMarker marker = null;

		if (exitValue == OpenCobolCompiler.SEGMENTATION_FAULT)
		{
			marker = new ProblemMarker("Segmentation fault", ProblemMarker.SEVERITY_ERROR);
		}
		else if (exitValue > 1)
		{
			marker = new ProblemMarker("unknown problem", ProblemMarker.SEVERITY_ERROR);
		}

		return marker;
	}

	private String readInput(InputStream in) throws IOException
	{
		String out = "";
		int buffer = 0;

		try
		{
			while ((buffer = in.read()) != -1)
			{
				out += (char) buffer;
			}
		}
		catch (IOException e)
		{
		}
		finally
		{
			if (in != null)
			{
				in.close();
				in = null;
			}
		}

		return out;
	}

	@Override
	protected boolean deleteOldBin(String binFile)
	{
		boolean success = true;

		try
		{
			this.runtime.exec("rm " + binFile);
		}
		catch (IOException e)
		{
			success = false;
		}

		return success;
	}

	@Override
	protected ProblemMarker processErrorLine(String errorLine)
	{
		ProblemMarker marker = null;
		String rawLine = null;
		String rawSeverity = null;
		String rawMessage = null;

		int severity = 0;
		int line = 0;

		StringTokenizer st = new StringTokenizer(errorLine, ":");

		if (st.countTokens() == 4)
		{
			st.nextToken();

			rawLine = st.nextToken();
			rawSeverity = st.nextToken();
			rawMessage = st.nextToken();

			try
			{
				line = Integer.parseInt(rawLine);
			}
			catch (NumberFormatException e)
			{
				line = 1;
			}

			rawSeverity = rawSeverity.trim();
			if (rawSeverity.equalsIgnoreCase("WARNING"))
			{
				severity = ProblemMarker.SEVERITY_WARNING;
			}
			else if (rawSeverity.equalsIgnoreCase("ERROR"))
			{
				severity = ProblemMarker.SEVERITY_ERROR;
			}
			else
			{
				severity = ProblemMarker.SEVERITY_INFO;
			}

			rawMessage = rawMessage.trim();

			marker = new ProblemMarker(rawMessage, severity, line);
		}
		else
		{
			rawMessage = errorLine.trim();
			severity = ProblemMarker.SEVERITY_INFO;

			marker = new ProblemMarker(rawMessage, severity);
		}

		return marker;
	}
}
